import { buffer, util } from '@kit.ArkTS';
import cryptoFramework from '@ohos.security.cryptoFramework';
import { ByteUtils } from '../../../backup/ByteUtils';
import byteUtils from './byteUtils';
import utils from './utils';
import { systemDateTime } from '@kit.BasicServicesKit';
import { DesUtils } from '../utils/DesUtils';

export class SupwisdomCodeUtils {
  readonly AES_ROOTKEY = 'Vbb1syh8U1+CdLmTVGdtDiVvKBQ81n4GmgBEO/ohSbU='
  readonly REQ_TOKEN_IVEC =
    [0x55, 0xb6, 0xf5, 0xb3, 0x28, 0x7c, 0x53, 0x5f, 0x82, 0x74, 0xb9, 0x93, 0x54, 0x67, 0x6d, 0x0e]
  readonly DIGITS_POWER_SHA256 = [1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000];

  /**
   *
   * @param uuid  byte数组
   * @param pwd   byte数组
   */
  requestDataConvert(uuid, pwd) {
    const uuidEn = this.aesCfb128EncryptWithPaddingMode(
      this.AES_ROOTKEY, this.REQ_TOKEN_IVEC, uuid)
    const pwdEn = this.aesCfb128EncryptWithPaddingMode(
      this.AES_ROOTKEY, this.REQ_TOKEN_IVEC, pwd)

    return {
      uuid: uuidEn,
      pwd: pwdEn
    }
  }

  /**
   * padding AES256|CFB128
   * @param cKey  AES_ROOTKEY
   * @param ivec  REQ_TOKEN_IVEC
   * @param data  byte数组
   */
  private aesCfb128EncryptWithPaddingMode(cKey, ivec, data) {
    const base64Helper = new util.Base64Helper()
    const aesKey = base64Helper.decodeSync(cKey)

    this.log("aesKey len = " + aesKey.length)
    this.log("aesKey = " + byteUtils.bytesToHexStr(aesKey))

    this.log(`data len = ${data.length}`)
    let paddingLen = 16 - (data.length % 16)
    if (paddingLen == 0) {
      paddingLen = 16
    }

    for (let i = 0; i < paddingLen; i++) {
      data.push(paddingLen)
    }

    const hex = ByteUtils.bytesToHexStr(data)
    this.log("data padding = " + hex)

    const enData = this.aesEncrypt(aesKey, ivec, data)
    this.log("enData = " + ByteUtils.bytesToHexStr(enData))

    const enBase64 = base64Helper.encodeToStringSync(enData)
    this.log("enBase64 = " + enBase64)

    return enBase64
  }

  /**
   *
   * @param aesKey  byte数组
   * @param ivec    byte数组
   * @param data    byte数组
   */
  private aesEncrypt(aesKey, ivec, data,
    opMode: cryptoFramework.CryptoMode = cryptoFramework.CryptoMode.ENCRYPT_MODE,
    transformation: string = 'CFB128') {
    const aesLength = aesKey.length * 8
    const algName = `AES${aesLength}`

    const symKey = cryptoFramework.createSymKeyGenerator(algName).convertKeySync({
      data: aesKey
    })
    let cipher = cryptoFramework.createCipher(`${algName}|${transformation}`)
    let ivDataBlob: cryptoFramework.DataBlob = {
      data: new Uint8Array(ivec)
    }
    let ivParams: cryptoFramework.IvParamsSpec = {
      algName: "IvParamsSpec",
      iv: ivDataBlob
    }
    cipher.initSync(opMode, symKey, ivParams)
    const enData = cipher.doFinalSync({
      data: new Uint8Array(data)
    }).data
    return enData
  }

  /**
   * v1版本生码
   * @param gid  string
   * @param schoolCode  string
   * @param outId   string
   * @param pKey    string
   * @param bTotpKey   byte[]
   * @param serverPubKey string
   * @param time number
   * @param stepLen float
   */
  buildToken(gid, schoolCode, outId, pKey, bTotpKey, serverPubKey, time, stepLen) {

    const base64Helper = new util.Base64Helper()

    //生成totp
    const minutesTime = Math.floor(time / 1000 / (stepLen * 60))
    let timeArray = new Uint8Array(new ArrayBuffer(8))
    utils.long2Char(timeArray, 8, minutesTime, 0)
    this.log("time = " + byteUtils.bytesToHexStr(timeArray))

    const digitLen = 8
    const totpCode = this.generateTOTPSha256(new Uint8Array(bTotpKey), timeArray, digitLen)
    let totpCodeStr = totpCode + ""
    while (totpCodeStr.length < 8) {
      totpCodeStr = "0" + totpCodeStr
    }
    this.log("totpCodeStr = " + totpCodeStr)

    //生成2位随机数
    const random1 = this.getRandomInt(0, 9)
    const random2 = this.getRandomInt(0, 9)

    //生成二维码原始值 学校代码+学号+totp+random1+random2
    const rawCode = `${schoolCode}:${outId}:${totpCodeStr}:${random1}${random2}`
    // const rawCode = `${schoolCode}:${outId}:${totpCodeStr}:85`
    this.log("rawCode = " + rawCode)
    const key = base64Helper.decodeSync(pKey)
    const data = new Uint8Array(byteUtils.stringToUtf8Bytes(rawCode))
    this.log("aes key = " + byteUtils.bytesToHexStr(key))
    this.log("aes data = " + byteUtils.bytesToHexStr(data))
    const enRawCode = this.aesEncrypt(key, this.REQ_TOKEN_IVEC, data)
    const enRawCodeBase64 = base64Helper.encodeToStringSync(enRawCode)
    this.log("enRawCode = " + enRawCodeBase64)
    const gidCipher = gid + ':' + enRawCodeBase64
    this.log("gidCipher = " + gidCipher)
    const data2 = new Uint8Array(byteUtils.stringToUtf8Bytes(gidCipher))
    const key2 = base64Helper.decodeSync(this.AES_ROOTKEY)
    const token = this.aesEncrypt(key2, this.REQ_TOKEN_IVEC, data2)
    const tokenBase64 = base64Helper.encodeToStringSync(token)
    this.log("token = " + tokenBase64)
    return tokenBase64
  }

  /**
   *
   * @param key  byte[]
   * @param data  byte[]
   * @param digitLen  int
   */
  private generateTOTPSha256(key, data, digitLen) {
    const hmac = cryptoFramework.createMac('SHA256')
    const symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256')
    const symKey = symKeyGenerator.convertKeySync({
      data: key
    })
    hmac.initSync(symKey)
    hmac.updateSync({
      data: data
    })
    const cHash = hmac.doFinalSync().data
    this.log("cHash = " + byteUtils.bytesToHexStr(cHash))
    const offset = cHash[cHash.length-1] & 0xf
    this.log(`offset = ${offset}`)

    const binary1 = ((cHash[offset] & 0x7F) << 24)
    const binary2 = ((cHash[offset+1] & 0xFF) << 16)
    const binary3 = ((cHash[offset+2] & 0xFF) << 8)
    const binary4 = (cHash[offset+3] & 0xFF)
    const binary = binary1 | binary2 | binary3 | binary4
    this.log(`binary = ${binary1},${binary2},${binary3},${binary4},${binary}`)
    const totp = binary % this.DIGITS_POWER_SHA256[digitLen]
    return totp
  }


  /**
   * v2版本生码
   * @param schoolCode
   * @param gid
   * @param outId
   * @param feetType
   * @param balance
   * @param rKey
   * @param pKey
   * @param dKey
   * @param iv
   * @param pSeed
   * @param seed32
   * @param time 秒
   * @returns
   */
  buildBarcode(schoolCode: string, gid: string, outId: string,
    feetType: number, balance: number,
    rKey: string, pKey: string, dKey: string,
    iv: string, pSeed: string, seed32: string, time: number) {

    const base64Helper = new util.Base64Helper()
    const dePKey = base64Helper.decodeSync(pKey)
    const deDKey = base64Helper.decodeSync(dKey)
    const ivBytes = byteUtils.hexStrToBytes(iv)
    const seed32Bytes = byteUtils.hexStrToBytes(seed32)
    const pSeedBytes = byteUtils.hexStrToBytes(pSeed)
    this.log("swV2 dePKey = " + byteUtils.bytesToHexStr(dePKey))
    this.log("swV2 deDKey = " + byteUtils.bytesToHexStr(deDKey))
    this.log("swV2 iv = " + byteUtils.bytesToHexStr(ivBytes))
    this.log("swV2 seed32 = " + byteUtils.bytesToHexStr(seed32Bytes))
    this.log("swV2 pSeed = " + byteUtils.bytesToHexStr(pSeedBytes))

    const mdRes = this.md5(schoolCode + gid)
    this.log("swV2 mdRes = " + byteUtils.bytesToHexStr(mdRes))

    const rKeyBytes = base64Helper.decodeSync(rKey)
    const deRKey = this.aesEncrypt(mdRes, ivBytes, rKeyBytes, cryptoFramework.CryptoMode.DECRYPT_MODE)
    this.log("swV2 deRKey = " + byteUtils.bytesToHexStr(deRKey))

    if (time == 0) {
      time = systemDateTime.getTime() / 1000
      time = Math.floor(time)
    }
    //生成原始字符
    const totp = this.totpCalc(new Uint8Array(seed32Bytes), 30, time, 8)
    this.log("swV2 totp = " + totp)
    const random = this.getRandomInt(0, 99)
    let plaintext = `${schoolCode}:${outId}:${totp}:${random}`
    this.log("swV2 plaintext = " + plaintext)
    //加密原始字符串
    const enData = this.aesEncrypt(dePKey, ivBytes, byteUtils.stringToUtf8Bytes(plaintext),
      cryptoFramework.CryptoMode.ENCRYPT_MODE, "CFB128")
    let ciphertext = base64Helper.encodeToStringSync(enData)
    this.log("swV2 ciphertext = " + ciphertext)
    //生成脱机码字符串
    const totp2 = this.totpCalc(new Uint8Array(pSeedBytes), 30, time, 6)
    this.log("swV2 totp2 = " + totp2)
    const signKey = this.calcSignKey(schoolCode, gid, totp2, deDKey)
    this.log("swV2 signKey = " + signKey)
    const barcodeKp = `${schoolCode}:${gid}:${outId}:${feetType}:${balance}:${totp2}{${signKey}}`.toUpperCase()
    this.log("swV2 raw barcodeKp = " + barcodeKp)
    const barcodeKpMd5 = this.md5(barcodeKp)
    const barcodeKpMd5HexString = byteUtils.bytesToHexStr(barcodeKpMd5)
    this.log("swV2 md5 barcodeKp = " + barcodeKpMd5HexString)
    plaintext = `${schoolCode}:${gid}:${outId}:${feetType}:${balance}:${totp2}:${barcodeKpMd5HexString}:${ciphertext}`
    this.log("swV2 plaintext = " + plaintext)
    //加密
    const enData2 = this.aesEncrypt(deRKey, ivBytes, byteUtils.stringToUtf8Bytes(plaintext))
    ciphertext = base64Helper.encodeToStringSync(enData2)
    this.log("swV2 ciphertext = " + ciphertext)
    return ciphertext
  }

  totpCalc(key: Uint8Array, step: number, time: number, digital: number) {

    this.log("totpCalc key = " + byteUtils.bytesToHexStr(key))
    this.log("totpCalc step = " + step)
    this.log("totpCalc time = " + time)
    this.log("totpCalc digital = " + digital)

    const timeStep = Math.floor(time / step)
    this.log("totpCalc time = " + timeStep)

    const byteArray = new Array()
    byteArray.push(0x00)
    byteArray.push(0x00)
    byteArray.push(0x00)
    byteArray.push(0x00)
    byteArray.push((timeStep >> 24) & 0xFF)
    byteArray.push((timeStep >> 16) & 0xFF)
    byteArray.push((timeStep >> 8) & 0xFF)
    byteArray.push(timeStep & 0xFF)

    this.log("totpCalc byteArray = " + byteUtils.bytesToHexStr(byteArray))

    const hmac = cryptoFramework.createMac('SHA256')
    const symKeyGenerator = cryptoFramework.createSymKeyGenerator('AES256')
    const symKey = symKeyGenerator.convertKeySync({ data: key })
    hmac.initSync(symKey)
    hmac.updateSync({ data: new Uint8Array(byteArray) })
    const hash = hmac.doFinalSync().data
    const offset = hash[hash.length-1] & 0xF
    let truncatedHash = 0
    for (let j = 0; j < 4; ++j) {
      truncatedHash <<= 8
      truncatedHash |= hash[offset + j]
    }
    truncatedHash &= 0x7FFFFFFF
    truncatedHash %= Math.pow(10, digital % 9)
    this.log(`totpCalc truncatedHash = ${truncatedHash}`)

    let result = truncatedHash + ""
    while (result.length < digital) {
      result = "0" + result
    }
    while (result.length > digital) {
      result = result.slice(-8)
    }
    return result
  }

  calcSignKey(schoolCode: string, gid: string, totp: string, dKey: Uint8Array) {
    const leftKey = dKey.slice(0, 8)
    const rightKey = dKey.slice(-8)
    const padding = [0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00]
    let offset = 0, i = 0, j = 0
    const temp = new Array(8), temp_enc = []
    let init_data = new Array(8)
    const macEnData = byteUtils.hexStrToBytes(totp)
    const macEnDataLen = macEnData?.length % 8
    const needPadding = macEnDataLen % 8 == 0
    while (offset < macEnDataLen) {
      for (i = 0; i < 8 && (i + offset) < macEnDataLen; i++) {
        temp[i] = init_data[i] ^ macEnData[offset+i];
      }
      for (j = i; i < 8; i++) {
        temp[i] = init_data[i] ^ padding[i -j];
      }
      const tempEnc = this.desEncrypt(leftKey, temp)
      init_data = byteUtils.hexStrToBytes(tempEnc)
      offset += 8;
    }
    if (needPadding) {
      for (i = 0; i < 8; i++) {
        temp[i] = init_data[i] ^ padding[i];
      }
      const tempEnc = this.desEncrypt(leftKey, temp)
      init_data = byteUtils.hexStrToBytes(tempEnc)
    }
    let tempEnc = this.desDecrypt(rightKey, init_data)
    init_data = byteUtils.hexStrToBytes(tempEnc)
    const sign = this.desEncrypt(leftKey, init_data)
    return sign.slice(0, 8)
  }

  desEncrypt(key, data) {
    this.log("desEncrypt key = " + byteUtils.bytesToHexStr(key))
    this.log("desEncrypt data = " + byteUtils.bytesToHexStr(data))
    const desUtils = new DesUtils()
    const result = desUtils.encryptDES_CBC(data, key, desUtils.ZERO_IVC)
    this.log("desEncrypt result = " + result)
    return result
  }

  desDecrypt(key, data) {
    this.log("desDecrypt key = " + byteUtils.bytesToHexStr(key))
    this.log("desDecrypt data = " + byteUtils.bytesToHexStr(data))
    const desUtils = new DesUtils()
    const result = desUtils.decryptDES_CBC(data, key, desUtils.ZERO_IVC)
    this.log("desDecrypt result = " + result)
    return result
  }

  md5(data) {
    const md = cryptoFramework.createMd('MD5')
    md.updateSync({ data: new Uint8Array(buffer.from(data, 'utf-8').buffer) })
    return md.digestSync().data
  }

  getRandomFloat(min: number, max: number): number {
    return Math.random() * (max - min) + min
  }

  getRandomInt(min: number, max: number): number {
    min = Math.ceil(min);
    max = Math.floor(max);
    return Math.floor(Math.random() * (max - min + 1)) + min;
  }

  log(msg) {
    console.log('SupwisdomCode：' + msg)
  }
}